﻿using System;

using Bettzueche.RLLibrary.Types;
using Bettzueche.RLLibrary.Functions;

namespace Bettzueche.RLLibrary.Component {
    /// <summary>
    /// 
    /// </summary>
    public interface IAgent {

        #region Properties
        
        /// <summary>
        /// Gets or sets the exploration policy.
        /// <para>(Optional)</para>
        /// </summary>
        /// <value>
        /// The exploration policy.
        /// </value>
        IExplorationPolicy Policy { get; set; }

        /// <summary>
        /// Gets or sets the policy pi.
        /// <para>(Optional)</para>
        /// </summary>
        /// <value>
        /// The pi.
        /// </value>
        StochasticPolicy Pi { get; set; }
        /// <summary>
        /// Gets or sets the environment.
        /// </summary>
        /// <value>
        /// The environment.
        /// </value>
        IEnvironment Environment { get; set; }

        /// <summary>
        /// Gets or sets the learn rate ('alpha').
        /// <para>(Optional)</para>
        /// </summary>
        /// <value>
        /// The learn rate.
        /// </value>
        double LearnRate { get; set; }
        /// <summary>
        /// Gets or sets the discount factor ('gamma').
        /// </summary>
        /// <value>
        /// The discount factor.
        /// </value>
        double DiscountFactor { get; set; }
        /// <summary>
        /// Gets or sets the trace decay ('lambda').
        /// <para>(Optional)</para>
        /// </summary>
        /// <value>
        /// The trace decay.
        /// </value>
        double TraceDecay { get; set; }

        /// <summary>
        /// Gets or sets the V function.
        /// </summary>
        /// <value>
        /// The V function.
        /// </value>
        IValueFunction VFunction { get; set; }

        /// <summary>
        /// Gets or sets the Q function.
        /// </summary>
        /// <value>
        /// The Q function.
        /// </value>
        IStateValueFunction QFunction { get; set; }

        /// <summary>
        /// Gets or sets the Eligibility function.
        /// </summary>
        /// <value>
        /// The E function.
        /// </value>
        IEligibilityFunction EFunction { get; set; }


        #endregion

        /// <summary>
        /// Inits the Agent.
        /// </summary>
        /// <param name="initialState">The initial state.</param>
        /// <param name="specifications">The specifications.</param>
        void Init(IState initialState, string specifications);

        /// <summary>
        /// Does the step.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <returns></returns>
        IAction DoStep(IAction action);

        /// <summary>
        /// Runs the episode.
        /// </summary>
        /// <param name="maxActionCount">The max action count.</param>
        /// <returns>Number of steps performed</returns>
        int RunEpisode(int maxActionCount);

        /// <summary>
        /// Runs the episode.
        /// </summary>
        /// <param name="finalStates">The final states.</param>
        /// <returns>Number of steps performed</returns>
        int RunEpisode(params IState[] finalStates);

        /// <summary>
        /// Runs the episode.
        /// </summary>
        /// <param name="maxActionCount">The max action count.</param>
        /// <param name="finalStates">The final states.</param>
        /// <returns>Number of steps performed</returns>
        int RunEpisode(int maxActionCount, params IState[] finalStates);

        /// <summary>
        /// Resets this instance.
        /// </summary>
        void Reset();
    }



    // ####### Generic Class #######################################

    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="S"></typeparam>
    /// <typeparam name="A"></typeparam>
    public abstract class GenericAgent<S,A> : IAgent
        where S : IState
        where A : IAction
    {
        #region Properties

        /// <summary>
        /// Gets or sets the exploration policy.
        /// <para>(Optional)</para>
        /// </summary>
        /// <value>
        /// The exploration policy.
        /// </value>
        public abstract GenericExplorationPolicy<A> Policy { get; set; }

        /// <summary>
        /// Gets or sets the environment.
        /// </summary>
        /// <value>
        /// The environment.
        /// </value>
        public abstract IEnvironment<S, A, double> Environment { get; set; }

        /// <summary>
        /// Gets or sets the V function.
        /// </summary>
        /// <value>
        /// The V function.
        /// </value>
        public abstract IValueFunction<S> VFunction { get; set; }

        /// <summary>
        /// Gets or sets the Q function.
        /// </summary>
        /// <value>
        /// The Q function.
        /// </value>
        public abstract IStateValueFunction<S, A> QFunction { get; set; }

        /// <summary>
        /// Gets or sets the Eligibility function.
        /// </summary>
        /// <value>
        /// The E function.
        /// </value>
        public abstract IEligibilityFunction<A, S> EFunction { get; set; }


        #endregion

        /// <summary>
        /// Inits the Agent.
        /// </summary>
        /// <param name="initialState">The initial state.</param>
        /// <param name="specifications">The specifications.</param>
        public abstract void Init(S initialState, string specifications);

        /// <summary>
        /// Does the step.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <returns></returns>
        public abstract A DoStep(A action);

        /// <summary>
        /// Runs the episode.
        /// </summary>
        /// <param name="maxActionCount">The max action count.</param>
        /// <returns>Number of steps performed</returns>
        public abstract int RunEpisode(int maxActionCount);

        /// <summary>
        /// Runs the episode.
        /// </summary>
        /// <param name="finalStates">The final states.</param>
        /// <returns>Number of steps performed</returns>
        public abstract int RunEpisode(params S[] finalStates);

        /// <summary>
        /// Runs the episode.
        /// </summary>
        /// <param name="maxActionCount">The max action count.</param>
        /// <param name="finalStates">The final states.</param>
        /// <returns>Number of steps performed</returns>
        public abstract int RunEpisode(int maxActionCount, params S[] finalStates);

        #region IAgent public Members

        /// <summary>
        /// Gets or sets the policy pi.
        /// <para>(Optional)</para>
        /// </summary>
        /// <value>
        /// The pi.
        /// </value>
        public abstract StochasticPolicy Pi {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the learn rate ('alpha').
        /// <para>(Optional)</para>
        /// </summary>
        /// <value>
        /// The learn rate.
        /// </value>
        public abstract double LearnRate {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the discount factor ('gamma').
        /// </summary>
        /// <value>
        /// The discount factor.
        /// </value>
        public abstract double DiscountFactor {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the trace decay ('lambda').
        /// <para>(Optional)</para>
        /// </summary>
        /// <value>
        /// The trace decay.
        /// </value>
        public abstract double TraceDecay {
            get;
            set;
        }

        /// <summary>
        /// Inits the Agent.
        /// </summary>
        /// <param name="initialState">The initial state.</param>
        /// <param name="specifications">The specifications.</param>
        /// <exception cref="System.NotSupportedException">initialState nicht vom Typ S.</exception>
        public void Init(IState initialState, string specifications) {
            if (initialState is S)
                this.Init((S)initialState, specifications);
        }

        /// <summary>
        /// Does the step.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <returns></returns>
        /// <exception cref="System.NotImplementedException"></exception>
        public IAction DoStep(IAction action) {
            if (action is A) {
                return this.DoStep((A)action);
            }
            throw new NotSupportedException("Parameter action must be type of"+typeof(A).Name);
        }

        /// <summary>
        /// Runs the episode.
        /// </summary>
        /// <param name="finalStates">The final states.</param>
        /// <returns>Number of steps performed</returns>
        /// <exception cref="System.NotSupportedException">Typ von finalStates nicht unterstützt.</exception>
        public int RunEpisode(params IState[] finalStates) {
            if (finalStates is S[])
                return this.RunEpisode(finalStates);
            throw new NotSupportedException("Typ von finalStates nicht unterstützt.");
        }

        /// <summary>
        /// Runs the episode.
        /// </summary>
        /// <param name="maxActionCount">The max action count.</param>
        /// <param name="finalStates">The final states.</param>
        /// <returns>Number of steps performed</returns>
        /// <exception cref="System.NotSupportedException">Typ von finalStates nicht unterstützt.</exception>
        public int RunEpisode(int maxActionCount, params IState[] finalStates) {
            if (finalStates is S[])
                return this.RunEpisode(maxActionCount, finalStates);
            throw new NotSupportedException("Typ von finalStates nicht unterstützt.");
        }

        /// <summary>
        /// Resets this instance.
        /// </summary>
        public abstract void Reset();

        #endregion


        #region IAgent hided Members

        IEnvironment IAgent.Environment {
            get {
                return this.Environment;
            }
            set {
                if (value is IEnvironment<S, A, double>)
                    this.Environment = (IEnvironment<S, A, double>)value;
                throw new NotSupportedException("Diese IEnvironment nicht unterstützt");
            }
        }

        IValueFunction IAgent.VFunction {
            get {
                throw new NotImplementedException();
            }
            set {
                throw new NotImplementedException();
            }
        }

        IStateValueFunction IAgent.QFunction {
            get {
                return this.QFunction;
            }
            set {
                if (value is IStateValueFunction<S, A>)
                    this.QFunction = (IStateValueFunction<S, A>)value;
                throw new NotSupportedException("Diese IStateValueFunction nicht unterstützt");
            }
        }

        IEligibilityFunction IAgent.EFunction {
            get {
                return this.EFunction;
            }
            set {
                if (value is IEligibilityFunction<A, S>)
                    this.EFunction = (IEligibilityFunction<A, S>)value;
                throw new NotSupportedException("Diese IEligibilityFunction nicht unterstützt");
            }
        }

        IExplorationPolicy IAgent.Policy {
            get {
                return this.Policy;
            }
            set {
                if (value is GenericExplorationPolicy<A>)
                    this.Policy = (GenericExplorationPolicy<A>)value;
                throw new NotSupportedException("Diese IExplorationPolicy nicht unterstützt");
            }
        }


        #endregion
    }

}
